////////////////////////////////////////////////////////////////////////////////
//  Copyright 2003 & onward LASMEA UMR 6602 CNRS/Univ. Clermont II
//  Copyright 2009 & onward LRI    UMR 8623 CNRS/Univ Paris Sud XI
//  Copyright (c) 2011 Bryce Lelbach
//
//  SPDX-License-Identifier: BSL-1.0
//  Distributed under the Boost Software License, Version 1.0. (See accompanying
//  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
////////////////////////////////////////////////////////////////////////////////

#include <cstdlib>
#include <string>
using namespace std;

struct registers_t
{
    uint32_t eax, ebx, ecx, edx;
};

#if defined __GNUC__
void __cpuid(registers_t& CPUInfo, uint32_t InfoType)
{
    __asm__ __volatile__(
        "cpuid"
        : "=a"(CPUInfo.eax), "=b"(CPUInfo.ebx), "=c"(CPUInfo.ecx), "=d"(CPUInfo.edx)
        : "a"(InfoType));
}

#elif defined _MSC_VER
# include <intrin.h>
#endif

bool has_bit_set(uint32_t value, uint32_t bit) { return (value & (1U << bit)) != 0; }

struct matcher
{
    uint32_t function;
    uint32_t registers_t::*reg;
    uint32_t bit;
    char const* target;
} options[] = {{0x0000'0001U, &registers_t::edx, 19, "clflush"},
    {0x0000'0001U, &registers_t::edx, 8, "cx8"}, {0x0000'0001U, &registers_t::ecx, 13, "cx16"},
    {0x0000'0001U, &registers_t::edx, 15, "cmov"}, {0x0000'0001U, &registers_t::edx, 5, "msr"},
    {0x0000'0001U, &registers_t::edx, 4, "rdtsc"}, {0x8000'0001U, &registers_t::edx, 27, "rdtscp"},
    {0x0000'0001U, &registers_t::edx, 23, "mmx"}, {0x0000'0001U, &registers_t::edx, 25, "sse"},
    {0x0000'0001U, &registers_t::edx, 26, "sse2"}, {0x0000'0001U, &registers_t::ecx, 0, "sse3"},
    {0x0000'0001U, &registers_t::ecx, 9, "ssse3"}, {0x0000'0001U, &registers_t::ecx, 19, "sse4.1"},
    {0x0000'0001U, &registers_t::ecx, 20, "sse4.2"}, {0x0000'0001U, &registers_t::ecx, 28, "avx"},
    {0x8000'0001U, &registers_t::edx, 11, "xop"}, {0x8000'0001U, &registers_t::edx, 16, "fma4"}};
size_t const noptions = sizeof options / sizeof options[0];

int main(int argc, char** argv)
{
    registers_t registers;
    if (argc < 2) return -1;

    string target(argv[1]);
    __cpuid(registers, 0x0000'0000);

    matcher m;
    size_t i = 0;
    for (i = 0; i < noptions; ++i)
    {
        if (target == options[i].target)
        {
            m = options[i];
            break;
        }
    }
    if (i >= noptions) return -2;

    __cpuid(registers, m.function);

    // exit with 0 if the bit is set
    return !has_bit_set(registers.*m.reg, m.bit);
}
